package org.ranki.moerae.clotho.node;

import org.ranki.moerae.clotho.context.Context;

import javax.annotation.PostConstruct;
import java.awt.font.TextHitInfo;
import java.lang.reflect.Field;
import java.util.*;

public abstract class AbstractNode implements Node{
    protected Set<AbstractNode> nexts=new HashSet<>();
    protected Set<AbstractNode> pres=new HashSet<>();
    class Rotue{
        AbstractNode next;
        int minTime=Integer.MAX_VALUE;

        public Rotue(AbstractNode next, int minTime) {
            this.next = next;
            this.minTime = minTime;
        }
    }
    protected Map<AbstractNode,Rotue> rotueMap=new HashMap<>();
    protected int aveCostTime=Integer.MAX_VALUE>>1;
    protected boolean hasBug=false;

    public Set<AbstractNode> getNexts() {
        return nexts;
    }

    public int getAveCostTime() {
        return aveCostTime;
    }

    @PostConstruct
    void linkToFathers(){
        Field[] fields= this.getClass().getDeclaredFields();
        for(Field field:fields){
            field.setAccessible(true);
            Object o= null;
            try {
                o = field.get(this);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
            if(o instanceof AbstractNode){
                AbstractNode abstractNode=(AbstractNode) o;
                abstractNode.nexts.add(this);
                this.pres.add(abstractNode);
            }
            field.setAccessible(false);
        }
    }
    protected void executeTree(Context ctx) {
        Set<AbstractNode> seen= (Set<AbstractNode>) ctx.getParams().getOrDefault("seen",new HashSet<AbstractNode>());
        if(!canDeal(ctx)){
            return;
        }
        run(ctx);
        seen.add(this);
        List<Node> list= (List<Node>) ctx.getGraph().getOrDefault(this,new ArrayList<>());
        for(AbstractNode node:nexts){
            if(!seen.contains(node)&&node.canDeal(ctx)){
                list.add(node);
            }
            node.executeTree(ctx.clone());
        }
        seen.remove(this);
        ctx.getParams().put("seen",seen);
        ctx.getGraph().put(this,list);
    }
    protected void executeFsm(Context ctx) {
        boolean hasNext=canDeal(ctx);
        AbstractNode curr=this;
        while (hasNext){
            curr.run(ctx);
            hasNext=false;
            for (AbstractNode next:curr.nexts){
                if(next.canDeal(ctx)){
                    curr=next;
                    hasNext=true;
                    break;
                }
            }
        }
    }
    protected void executeSearch(Context ctx){
        AbstractNode end= (AbstractNode) ctx.getParams().get("end");
        AbstractNode curr=this;
        do{
            if(!curr.canDeal(ctx)){
                preDeal(ctx);
                hasBug=true;
                postDeal(ctx);
            }else {
                curr.run(ctx);
            }
            if(curr.hasBug){
                return;
            }
            curr=curr.rotueMap.get(end).next;
        }while (curr!=end);
    }
    @Override
    public void execute(Context ctx, Type type) {
        if(Type.FSM.equals(type)){
            executeFsm(ctx);
        }else if(Type.TREE.equals(type)){
            executeTree(ctx);
        }else if(Type.SEARCH.equals(type)){
            executeSearch(ctx);
        }
    }
    protected void preDeal(Context ctx){
        long time=System.currentTimeMillis();
        ctx.getParams().put("time",time);
    }
    protected void backTrace(){
        for(AbstractNode pre:this.pres){
            if(pre.renew(this)){
                pre.renew();
                pre.backTrace();
            }
        }
    }
    protected boolean renew(AbstractNode curr){
        boolean flag=false;
        if(aveCostTime==Integer.MAX_VALUE>>1){
            return flag;
        }
        if(!this.rotueMap.containsKey(curr)){
            this.rotueMap.put(curr,new Rotue(curr,aveCostTime));
            flag=true;
        }
        for(AbstractNode end:curr.rotueMap.keySet()){
            Rotue rotue=this.rotueMap.getOrDefault(end,new Rotue(curr,Integer.MAX_VALUE>>1));
            if(rotue.next==curr){
                rotue.minTime=curr.rotueMap.get(end).minTime+this.aveCostTime;
                flag=true;
            }
            this.rotueMap.put(end,rotue);
        }
        return flag;
    }
    protected void renew(){
        for(AbstractNode next:this.nexts){
            for(AbstractNode end:next.rotueMap.keySet()){
                Rotue rotue=this.rotueMap.getOrDefault(end,new Rotue(null,Integer.MAX_VALUE>>1));
                if(rotue.minTime>next.rotueMap.get(end).minTime+aveCostTime){
                    rotue.next=next;
                    rotue.minTime=next.rotueMap.get(end).minTime+aveCostTime;
                    this.rotueMap.put(end,rotue);
                }
            }
        }
    }
    protected void postDeal(Context ctx){
        Set<AbstractNode> seen= (Set<AbstractNode>) ctx.getParams().get("seen");
        long time= (long) ctx.getParams().get("time");
        time=System.currentTimeMillis()-time;
        int change=0;
        if(aveCostTime==Integer.MAX_VALUE>>1){
            aveCostTime= (int) time;
            change=aveCostTime;
        }else if(aveCostTime>Integer.MAX_VALUE>>1){
            change= (int) (time-aveCostTime);
            aveCostTime=(int) time;
        }
        else{
            aveCostTime= (int) ((aveCostTime+time)>>1);
            change= (int) (time-aveCostTime);
        }
        if(hasBug){
            change=Integer.MAX_VALUE>>1;
            aveCostTime+=Integer.MAX_VALUE>>1;
        }
        if(Math.abs(change)>=0.2*aveCostTime||!seen.contains(this)||hasBug){
            for(Rotue rotue:rotueMap.values()){
                rotue.minTime+=change;
            }
            this.backTrace();
        }
    }
    public void run(Context ctx){
        hasBug=false;
        preDeal(ctx);
        try {
            deal(ctx);
        }catch (Exception e){
            hasBug=true;
        }
        postDeal(ctx);
    }
    protected abstract void deal(Context ctx);
    public abstract boolean canDeal(Context ctx);
}
